查看原文
其他

DIY USB 电流表(7):读取和显示 INA219 电流电压数据

ohdarling 欧大的自留地 2024-07-04

在前一篇 《DIY USB 电流表(6):点个屏,使用 I2C 驱动 0.96 寸 OLED》 中,我们已经完成了屏幕显示驱动的开发,并且根据需求,列出了需要展示的数据项,确定了一下最终显示内容的布局。

在之前,显示的内容都是占位的测试数据,在这一节,就可以开始真正去读取 INA219 传感器的数据,将电路中测量的电压、电流等数据显示在屏幕上,这又是一节枯燥的编码工作 🙈。

PS. 我也还是一个初学者,如果文章中有一些错误或不足,还请多多指教。


准备工作

在开始读取 INA219 的数据之前,同样也要准备一些相关的库,例如 INA219 数据的读取、参数的配置,以及在 CH32V003-GameConsole 中所使用的 I2C 封装并未提供读取 I2C 数据方法,另外,为了计算 USB 电流表运行时间内消耗的电量,也需要有相关的计时方法。

I2C 读取方法

在 CH32V003-GameConsole 的代码中,已经有了 i2c_tx.h,提供了 I2C 写入相关的方法,对于一个游戏机来说,拥有写入方法就足够了,它只需要用 I2C 来刷新屏幕,但是在我们的 USB 电流表项目中,还需要使用 I2C 去读取 INA219 的数据,因此需要去封装一个 I2C 读取方法。

在开始编写 I2C 读取方法之前,先将 i2c_tx.c 文件中初始化 I2C 控制器的代码修改一下,启用自动发送 ACK 的标志位,防止后续读取失败。

配置完后,在 i2c_tx.h 中添加 I2C 读取相关方法的定义:

// 开始读取 I2C 数据void I2C_start_read(uint8_t addr);// 从 I2C 总线读取 1 个字节uint8_t I2C_read();// 停止读取 I2C 数据void I2C_read_stop(void);

接下来完成相关 I2C 读取方法的编写,在 i2c_tx.c 文件的末尾添加以下代码:

// 根据地址设置写入标志位#define OADDR1_ADD0_Set ((uint8_t)0x01)// I2C 控制器读取状态#define I2C_RECV_MODE_SELECTED ((uint32_t)0x00020003) /* BUSY, MSL and ADDR flags */
void I2C_start_read(uint8_t addr) { addr |= OADDR1_ADD0_Set;
while(I2C1->STAR2 & I2C_STAR2_BUSY); // wait until bus ready I2C1->CTLR1 |= I2C_CTLR1_START; // set START condition while(!(I2C1->STAR1 & I2C_STAR1_SB)); // wait for START generated I2C1->DATAR = addr; // send slave address + R/W bit while(!I2C_checkEvent(I2C_RECV_MODE_SELECTED)); // wait for address transmitted}
uint8_t I2C_read() { while (!(I2C1->STAR1 & I2C_STAR1_RXNE)); return I2C1->DATAR;}
void I2C_read_stop(void) { while(!(I2C1->STAR1 & I2C_STAR1_RXNE)); // wait for last byte transmitted I2C1->CTLR1 |= I2C_CTLR1_STOP; // set STOP condition}

完成这些之后,就可以开始适配一下 INA219 的驱动到 CH32V003 上了。

INA219 驱动适配

INA219 的驱动,我使用了一个之前在 ESP32 上使用的库 INA219_WE,将它稍微变化一下,适配到 CH32V003 上来使用。

项目地址:https://github.com/wollewald/INA219_WE

将项目代码下载下来之后,我们只需要使用 INA219_WE.hINA219_WE.cpp 两个文件,将它们复制到 USB 电流表固件项目的 src/drivers 目录中。

接下来的适配工作,主要变更的有以下几个地方:

  • 去除 Arduino 相关的头文件

  • 构造方法仅保留一个传入 I2C 地址的方法

  • 补全缺失的 Arduino 中相关方法

  • writeRegisterreadRegister 适配为 i2c_tx.c 中的相关方法

前面两点比较好修改,在 INA219_WE.h 中删除对应的代码就可以,就不多赘述,接下来的修改都在 INA219_WE.cpp 中完成。

添加头文件和缺失定义

因为默认情况下,项目中没有 byte 类型、abs、delayMicroseconds 这三个的定义,因此需要提供定义一下。

#include <debug.h>#include "i2c_tx.h"
#define byte uint8_t#define abs(v) (v > 0 ? v : -v)#define delayMicroseconds(ms) Delay_Ms(ms)

适配 wrigeRegister 和 readRegister

将原代码中的这两个方法替换成以下内容,主要是将 Wire 相关的调用,替换成 i2c_tx.h 中相关方法的调用:

uint8_t INA219_WE::writeRegister(uint8_t reg, uint16_t val){ I2C_start(i2cAddress); uint8_t lVal = val & 255; uint8_t hVal = val >> 8; I2C_write(reg); I2C_write(hVal); I2C_write(lVal); I2C_stop(); return 0;}
uint16_t INA219_WE::readRegister(uint8_t reg){ uint8_t MSByte = 0, LSByte = 0; uint16_t regValue = 0;
I2C_start(i2cAddress); I2C_write(reg); I2C_stop();
I2C_start_read(i2cAddress); MSByte = I2C_read(); LSByte = I2C_read(); I2C_read_stop();
regValue = (MSByte<<8) + LSByte; return regValue;}

这样就完成了 INA219 驱动的适配,可以直接通过 INA219_WE 的类来读取 INA219 传感器数据了。

获取系统运行时间

为了计算 USB 电流表统计的电量,除了功率之后,还需要运行对应功率的时间,在默认情况下,CH32V003 SDK 并未提供类似 Arduino 中 millis() 这样的方法来获取系统运行时间,因此我们需要额外编写一个方法用来获取运行时间。

这个可以从 arduino_core_ch32 项目中获取实现,基于系统的 Tick 中断计数来实现运行时间的统计。

代码地址:https://github.com/Community-PIO-CH32V/arduino_core_ch32/blob/main/cores/arduino/ch32/clock.c

void systick_init(void){ SysTick->SR = 0; SysTick->CTLR= 0; SysTick->CNT = 0; SysTick->CMP = SystemCoreClock / 1000 - 1; SysTick->CTLR= 0xF; NVIC_SetPriority(SysTicK_IRQn,0xFF); NVIC_EnableIRQ(SysTicK_IRQn);}

有了获取运行时间的方法之后,就可以计算累计消耗电量了。


读取 INA219 数据并显示

万事俱备,只欠东风,准备好所有驱动和相关依赖之后,就可以读取真实 INA219 的传感器数据了。

我们将 INA219 数据读取、计算相关的代码,都放一个独立的文件中来管理,就叫 data_manager.hpp 吧。

定义数据存储变量

在这里定义了采集电阻大小为 10mΩ,另外可以看到这里定义了 INA219 的 I2C 为 0x40 <<,这是因为在从 CH32V003-GameConsole 拿来使用的 I2C 驱动,是直接将读写位交给外部控制了,因此这里需要提前左移一位。

#include "drivers/INA219_WE.h"#include <string.h>#include "drivers/clock.h"
#define INA219_I2C_ADDRESS (0x40 << 1)#define SHUNT_SIZE_IN_MR 10
INA219_WE ina219(INA219_I2C_ADDRESS);
int32_t measure_vbus_mv = 0;int32_t measure_vshunt_uv = 0;int32_t measure_current_ma = 0;int32_t measure_power_mw = 0;int32_t measure_cap_mwh = 0;int32_t measure_cap_last_time = 0;

初始化 INA219 驱动

作为一个电流表,我们需要持续监测整个电路和功率,因此在这里将 INA219 初始化为持续采样模式。

void adc_setup() { ina219.init(); ina219.setMeasureMode(CONTINUOUS); ina219.setPGain(PG_80); ina219.setShuntSizeInOhms(SHUNT_SIZE_IN_MR / 1000);}

另外这里将 INA219 的增益模式设置为 PG_80,根据数据手册,表示测量压差范围为 80mV:

这里使用的采样电阻大小为 10mΩ,在 PD 2.0 100W 的情况下,最大电流为 5A,通过计算 5A * 10mΩ = 50mV 可以得出最大压降为 50mV,因此设置测量范围为 80mV 完全够用。

读取数据并计算功率、电量

这里采用积分的方式来计算累计的电量,将当前计算出来的功率视为从上一次计算到当前时间点的持续功率,从而计算出这段时间的电量,再每次进行累计。

void adc_read_values() { measure_vbus_mv = ina219.getBusVoltage_V() * 1000; measure_vshunt_uv = ina219.getShuntVoltage_mV() * 1000; if (measure_vshunt_uv < 0) { measure_vshunt_uv = 0; } measure_current_ma = measure_vshunt_uv / SHUNT_SIZE_IN_MR; measure_power_mw = measure_vbus_mv * measure_current_ma / 1000;
uint32_t now = millis(); uint32_t period = now - measure_cap_last_time; measure_cap_last_time = now; int32_t period_cap = measure_power_mw * period / 1000;
    measure_cap_mwh += period_cap;}

显示数据

在这里先写一个 show_power_metrics 方法将刷新屏幕,显示数据相关的操作封装起来。

这里显示数据就比较简单,将电压、电流、功率、电量四个数字格式化到缓冲区中,并通过显示驱动显示到每一行上。

void show_power_metrics() { char buf[17]; display_clear();
snprintf(buf, 17, (const char *)"Volt : %dmV", (int)measure_vbus_mv); display_write_line(0, buf);
snprintf(buf, 17, (const char *)"Curr : %dmA", (int)measure_current_ma); display_write_line(1, buf);
snprintf(buf, 17, (const char *)"Power: %dmW", (int)measure_power_mw); display_write_line(2, buf);
snprintf(buf, 17, (const char *)"Cap : %dmWh", (int)(measure_cap_mwh / 3600)); display_write_line(3, buf);
display_flush();}


持续更新数据

最终,我们修改一下 main 方法,在死循环中持续读取 INA219 并更新到屏幕上,就完成了 USB 电流表的核心功能。

int main(void){ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); SystemCoreClockUpdate(); Delay_Init(); systick_init();
// 初始化屏幕驱动 display_init(); // 初始化 INA219 驱动 adc_setup();
while (1) { // 读取 INA219 adc_read_values(); // 刷新屏幕显示 show_power_metrics(); }}

实际运行效果

在这里,将固件烧录进 USB 电流表,并且在输出端接上负载,就可以在屏幕上看到当前负载消耗的实时功率了。

这里使用的电源输入是 5V,可以看到 USB 电流表检测到的电压只有 4.8V 左右,这是因为通常 USB 电缆都有内阻,在有电流时,会有压降,最终到达负载时的电压就跟电源端的电压不一致了。

一般来说,更好的 USB 线材会拥有更低的内阻,在大功率充电的场景中,也会拥有发热更低的优势。


小结

至此,我们已经完成了 DIY USB 电流表的核心功能,显示电压、电流等数据,如果不需要额外的功能,在这个步骤已经可以算是完工了 😃。

当然在硬件上我们已经预留了两个按钮,还是可以做一些其他功能进来,例如显示功率曲线?这样就可以在给设备充电过程中观察到充电功率的变化了。

那么,下一篇来完成记录功率历史和绘制功率曲线。


USB 电流表开源地址

这个 USB 电流表所有资料已经开源,可以在以下仓库中获取,包含固件代码、PCB 生产 Gerber 文件、原理图和外壳 STL 文件。

https://github.com/ohdarling/CH32V003-USBMeter

硬件相关的源文件已经在立创开源平台开源,访问以下地址可以进行一键 PCB 下单和一键 BOM 配单操作:

https://oshwhub.com/wandaeda/ji-yu-ch32v003-de-usb-dian-liu-biao


DIY USB 电流表系列


其他 DIY 项目

30 元 DIY 一个柔性灯丝氛围灯

教程地址: https://xujiwei.com/blog/2024/04/diy-ambient-light/


参考资料

  • https://github.com/wagiminator/CH32V003-GameConsole

  • https://github.com/wollewald/INA219_WE

  • https://github.com/ohdarling/CH32V003-USBMeter

  • https://oshwhub.com/wandaeda/ji-yu-ch32v003-de-usb-dian-liu-biao

  • https://github.com/Community-PIO-CH32V/arduino_core_ch32

  • https://www.ti.com/product/INA219

  • https://www.ti.com/lit/ds/symlink/ina219.pdf



继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存